#pragma once
#include "common/limitation.h"
#include "common/exceptions.h"

#define DEBUG_PILO_MEMORY_TCFIXEDBUFFER

#ifdef DEBUG_PILO_MEMORY_TCFIXEDBUFFER
#include "string/stringoutput.h"
#endif

#include "./buffer.h"

namespace PILO 
{
    namespace Core
    {
        namespace Memory 
        {
            namespace Buffer
            {
                template<typename T, size_t bufferCapacity> class TCFixedBuffer
                {
                protected: //variables
                    size_t  _m_indexForReading;
                    size_t  _m_indexForWriting;
                    size_t  _m_capacity;
                    T   _m_buffer[bufferCapacity];
                    T*  _m_bufferPtr;

                public: //methods 
                    TCFixedBuffer()
                    {
                        if (bufferCapacity > MC_LIMIT_TCFIXEDBUFFER_MAX_ELEMENT_NUMBER) {
                            throw EC_REACH_UPPER_LIMIT;
                        }
                        if (bufferCapacity * sizeof(T) > MC_LIMIT_TCFIXEDBUFFER_MAX_DATA_SIZE) {
                            throw EC_REACH_UPPER_LIMIT;
                        }
                        _m_indexForReading = 0;
                        _m_indexForWriting = 0;
                        _m_capacity = bufferCapacity;
                        _m_bufferPtr = _m_buffer;
                    }

                    void attach(T* p, size_t capacity, size_t dataPosition, size_t dataLength)
                    {
                        if (capacity > MC_LIMIT_TCFIXEDBUFFER_MAX_ELEMENT_NUMBER) {
                            throw EC_REACH_UPPER_LIMIT;
                        }
                        if (capacity * sizeof(T)> MC_LIMIT_TCFIXEDBUFFER_MAX_DATA_SIZE) {
                            throw EC_REACH_UPPER_LIMIT;
                        }

                        ASSERT(dataPosition + dataLength <= capacity);
                        _m_bufferPtr = (T*) p;
                        _m_capacity = capacity;
                        if (dataLength > 0)
                        {
                            _m_indexForReading = dataPosition;
                            _m_indexForWriting = dataPosition + dataLength;
                        } else {
                            _m_indexForReading = 0;
                            _m_indexForWriting = 0;
                        }

                    }

                    size_t elementSize() const
                    {
                        return sizeof(T);
                    }

                    size_t internalBufferLength() const
                    {
                        return _m_capacity;
                    }

                    const T* internalBuffer() const
                    {
                        return &_m_bufferPtr[0];               
                    }

                    const T* dataForReading() const
                    {
                        size_t  pos = this->indexForReading();
                        if (pos != MC_INVALID_SIZE) {
                            return &_m_bufferPtr[pos];
                        }
                        return static_cast<T*>(0);        
                    }

                    const T* bufferForWriting() const
                    {
                        size_t  pos = this->indexForWriting();
                        if (pos != MC_INVALID_SIZE) {
                            return &_m_bufferPtr[pos];
                        }
                        return static_cast<T*>(0); 
                    }

                    size_t dataSize() const
                    {
                        return (_m_indexForWriting - _m_indexForReading) * sizeof(T);
                    }

                    size_t indexForReading() const
                    {
                        if (_m_indexForWriting - _m_indexForReading > 0) {
                            return _m_indexForReading;
                        }
                        return MC_INVALID_SIZE;
                    }

                    size_t indexForWriting() const
                    {
                        if (_m_indexForWriting < _m_capacity) {
                            return _m_indexForWriting;
                        }
                        return MC_INVALID_SIZE;               
                    }

                    size_t readAwailable() const
                    {
                        return _m_indexForWriting - _m_indexForReading;
                    }

                    size_t writeAwaiable() const
                    {
                        return _m_capacity - _m_indexForWriting;
                    }

                    size_t read(T* userBuffer, size_t requiredReadLength, DWORD flag=0)
                    {
                        size_t availableCount = this->readAwailable();
                        if (availableCount < requiredReadLength) {
                            if (flag & Buffer::EnumNoopOnPartial) {//need full data at one call
                                return MC_INVALID_SIZE; //bailing out
                            }
                            else if (availableCount < 1) {
                                return 0;
                            }
                            requiredReadLength = availableCount;
                        }
                        memcpy(userBuffer, &_m_bufferPtr[_m_indexForReading], requiredReadLength*sizeof(T));
                        this->_m_indexForReading += requiredReadLength;
                        return requiredReadLength;
                    }

                    size_t write(const T* userBuffer, size_t requiredWriteLength, DWORD flag=0)
                    {
                        size_t availableCount = this->writeAwaiable();
                        if (availableCount < requiredWriteLength) {
                            if (flag & Buffer::EnumNoopOnPartial) {
                                return MC_INVALID_SIZE;
                            }
                            else if (availableCount < 1) {
                                return 0;
                            }
                            requiredWriteLength = availableCount;
                        }
                        memcpy(&_m_bufferPtr[_m_indexForWriting] , userBuffer, requiredWriteLength * sizeof(T));
                        _m_indexForWriting += requiredWriteLength;
                        return requiredWriteLength;
                    }

                    void shrink()
                    {
                        if (_m_indexForReading > 0) {
                            if (_m_indexForWriting > _m_indexForReading) {
                                memmove(&_m_bufferPtr[0], &_m_bufferPtr[_m_indexForReading], (_m_indexForWriting - _m_indexForReading) * sizeof(T));
                                _m_indexForWriting -= _m_indexForReading;
                                _m_indexForReading = 0;

                            } else {
                                _m_indexForReading = 0;
                                _m_indexForWriting = 0;
                            }
                        }
                    }

                    void reset()
                    {
                        _m_indexForReading = 0;
                        _m_indexForWriting = 0;
                    }

                    size_t peek(T* userBuffer, size_t requiredReadLength, DWORD flag=0)
                    {
                        size_t  originalRp = _m_indexForReading;

                        size_t nRead = this->read(userBuffer, requiredReadLength, flag);
                        _m_indexForReading = originalRp;

                        return nRead;
                    }

                    //operators
                    T& operator [] (size_t index)
                    {
                        size_t dataLength = _m_indexForWriting - _m_indexForReading;
                        ASSERT(index < dataLength );
                        return _m_bufferPtr[index];
                    }

#ifdef DEBUG_PILO_MEMORY_TCFIXEDBUFFER
                    void debugObjectInformationToConsole()
                    {
                        FormatOutputToConsole(TEXT("\nObject Information:\n"));
                        FormatOutputToConsole(TEXT(" > Type=[TCFixedBuffer]Size=(%u) addr=(0x%p) ElementSize=(%u)\n"), sizeof(*this), this, this->elementSize());
                        FormatOutputToConsole(TEXT(" > BufferBase=(0x%p) BufferLength=(%u:%u)\n"), this->internalBuffer(),this->internalBufferLength(),this->internalBufferLength()*this->elementSize());
                        FormatOutputToConsole(TEXT(" > DataForRead=(0x%p:%u) ReadAvailable=(%u)\n"), this->dataForReading(), this->indexForReading(), this->readAwailable());
                        FormatOutputToConsole(TEXT(" > BufferForWrite=(0x%p:%u) WriteAvailable=(%u)\n"),this->bufferForWriting(), this->indexForWriting(),this->writeAwaiable());
                        //                     if (this->elementSize() <= 4)
                        //                     {
                        //                         PILOPrintf(TEXT(" > Try to show content:"));
                        //                         for (size_t index=0; index<this->readAwailable(); index++)
                        //                         {
                        //                             if ((index % 8 == 0)) {
                        //                                 PILOPrintf(TEXT("\n >  "));
                        //                             }
                        //                             int c = static_cast<int>(this->dataForReading()[index]);
                        //                             if (! isprint(c)) {
                        //                                 PILOPrintf(TEXT("%02X "), c);
                        //                             } else {
                        //                                 PILOPrintf(TEXT("[%c] "),c);
                        //                             }
                        //                         }
                        //                         PILOPrintf(TEXT("\n"));
                        //                     }

                    }
#endif

                };

            }
            
        }
    }    
}
